<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="coinfaces">

        <title>部署 Django</title>

    <link rel="stylesheet" href="../theme/css/style.css">
    <link rel="stylesheet" href="../theme/css/pygments.css">



</head>

<body>
    <div class="content">
<h1>部署 Django</h1>    <p>
        under
            <a href="../tags/apache/">Apache</a>
            <a href="../tags/django/">Django</a>
            <a href="../tags/mysql/">MySQL</a>
    </p>
    <p>
        in <a href="../categories/uncategorized/">Uncategorized</a>
    </p>
    <p>Published: 2019-04-15</p>


    <!-- wp:heading -->

<h2>准备服务器运行环境</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->

<p>在开发机上运行 pip freeze &gt;frozen_requirements.txt | grep -v
\^pkg_resources，将其和包含 Django 应用的目录一起上传到服务器。创建
virtualenv 环境，运行 pip install -r frozen_requirements.txt
确保同样的运行环境。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->

<h2>调整 Django 站点设置</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->

<p>有2处地方需要用到 settings 模块，manage.py 和 wsgi.py。这里不对
manage.py 和 wsgi.py 作任何修改，只修改 settings.py
的代码，在其末尾加上一行：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="k">try</span><span class="p">:</span>
    <span class="kn">from</span> <span class="nn">.production</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
    <span class="k">pass</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>如果有需要根据 DEBUG 的值来执行不同的操作的代码，放到上述代码后面。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>以 production.py 的存在作为业务运行的依据，在 production.py 中覆盖
settings.py 中的设置。首先 DEBUG 必须设置为 False，SECRET_KEY
必须保密，ALLOWED_HOSTS 必须有内容。</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">DEBUG = False</span>
<span class="err">SECRET_KEY = &#39;something secret&#39;</span>
<span class="err">ALLOWED_HOSTS = [&#39;A&#39;, &#39;list&#39;,]</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>需要比 SQLite 更强大的数据库：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">DATABASES = {</span>
<span class="err">    &#39;default&#39;: {</span>
<span class="err">        &#39;ENGINE&#39;: &#39;django.db.backends.mysql&#39;,</span>
<span class="err">        &#39;NAME&#39;: &#39;name_of_the_database&#39;,</span>
<span class="err">        &#39;USER&#39;: &#39;name_of_the_user&#39;,</span>
<span class="err">        &#39;PASSWORD&#39;: &#39;password&#39;,</span>
<span class="err">        &#39;OPTIONS&#39;: {</span>
<span class="err">            &#39;init_command&#39;: &quot;SET sql_mode=&#39;STRICT_TRANS_TABLES&#39;&quot;,</span>
<span class="err">        },</span>
<span class="err">    }</span>
<span class="err">}</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>sql_mode='STRICT_TRANS_TABLES'
在数据表中数据存放不下的情况下报错，而不是仅仅显示警告信息，详见<a href="https://docs.djangoproject.com/en/2.2/ref/databases/#setting-sql-mode">文档</a>。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>启用日志：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">LOGGING = {</span>
<span class="err">    &#39;version&#39;: 1,</span>
<span class="err">    &#39;disable_existing_loggers&#39;: False,</span>
<span class="err">    &#39;handlers&#39;: {</span>
<span class="err">        &#39;file&#39;: {</span>
<span class="err">            &#39;level&#39;: &#39;DEBUG&#39;,</span>
<span class="err">            &#39;class&#39;: &#39;logging.FileHandler&#39;,</span>
<span class="err">            &#39;filename&#39;: os.path.join(os.path.dirname(__file__), &#39;..&#39;, &#39;logs&#39;, &#39;debug.log&#39;),</span>
<span class="err">        },</span>
<span class="err">    },</span>
<span class="err">    &#39;loggers&#39;: {</span>
<span class="err">        &#39;django&#39;: {</span>
<span class="err">            &#39;handlers&#39;: [&#39;file&#39;],</span>
<span class="err">            &#39;level&#39;: &#39;DEBUG&#39;,</span>
<span class="err">            &#39;propagate&#39;: True,</span>
<span class="err">        },</span>
<span class="err">    },</span>
<span class="err">}</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>启用 manage.py check --deploy 提示的安全选项：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">SECURE_SSL_REDIRECT = True</span>
<span class="err">CSRF_COOKIE_SECURE = True</span>
<span class="err">SESSION_COOKIE_SECURE = True</span>
<span class="err">SECURE_CONTENT_TYPE_NOSNIFF = True</span>
<span class="err">SECURE_BROWSER_XSS_FILTER = True</span>
<span class="err">X_FRAME_OPTIONS = &#39;DENY&#39;</span>
<span class="err">SECURE_HSTS_SECONDS = 0</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>除了 HSTS 选项，因为我没理解它是做什么的。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->

<h2>创建和设置 MySQL 数据库</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->

<p>以 root 身份启动 mysql 命令行：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">sudo mysql -u root -p</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>创建和设置 django 应用数据库和及其用户（替换 &lt;&gt; 中的内容）：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="n">CREATE</span> <span class="n">DATABASE</span> <span class="o">&lt;</span><span class="n">DB_NAME</span><span class="o">&gt;</span> <span class="n">DEFAULT</span> <span class="n">CHARACTER</span> <span class="n">SET</span> <span class="n">utf8mb4</span> <span class="n">COLLATE</span> <span class="n">utf8mb4_unicode_ci</span><span class="p">;</span>

<span class="n">CREATE</span> <span class="n">USER</span> <span class="s">&quot;&lt;DB_USER&gt;&quot;@&quot;localhost&quot;</span> <span class="n">IDENTIFIED</span> <span class="n">BY</span> <span class="s">&quot;&lt;PASSWORD&gt;&quot;</span><span class="p">;</span>

<span class="n">GRANT</span> <span class="n">ALL</span> <span class="n">PRIVILEGES</span> <span class="n">ON</span> <span class="o">&lt;</span><span class="n">DB_NAME</span><span class="o">&gt;</span><span class="p">.</span><span class="o">*</span> <span class="n">TO</span> <span class="s">&quot;&lt;DB_USER&gt;&quot;@&quot;localhost&quot;</span> <span class="n">IDENTIFIED</span> <span class="n">BY</span> <span class="s">&quot;&lt;DB_PASSWORD&gt;&quot;</span><span class="p">;</span>

<span class="n">FLUSH</span> <span class="n">PRIVILEGES</span><span class="p">;</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>安装 mysqlclient 包使 Django 可以连接 MySQL 数据库：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">apt-get install python3-dev gcc default-libmysqlclient-dev</span>
<span class="err">pip install mysqlclient</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:heading -->

<h2>设置 apache2 和 mod_wsgi</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->

<p>安装 mod_wsgi。</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">apt-get install libapache2-mod-wsgi-py3</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>在 /etc/apache2/apache2.conf 中添加 WSGI 运行配置：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">WSGIPythonHome /path/to/venv/directory</span>
<span class="err">WSGIPythonPath /path/to/mysite.com</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>在 apache2 的站点配置中添加：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code>WSGIScriptAlias /api /path/to/mysite.com/mysite/wsgi.py

<span class="nt">&lt;Directory</span> <span class="err">/path/to/mysite.com/mysite</span><span class="nt">&gt;</span>
  <span class="nt">&lt;Files</span> <span class="err">wsgi.py</span><span class="nt">&gt;</span>
    Require all granted
  <span class="nt">&lt;/Files&gt;</span>
<span class="nt">&lt;/Directory&gt;</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>这里把 DRF 安装到 /api/ 路径下，原站点启用了 HTTPS，这里就不需要再设置
HTTPS 证书了。注意 WSGIScriptAlias 后面的 /api/ 不能写成 /api/ 否则
apache2 会拒绝对 /api/ 子路径（如 /api/users/）的访问。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->

<h2>其它问题</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->

<p>在一台新的服务器上部署后，总结了一些新的问题：</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>该服务器的 MySQL 不是发行版（CentoOS 7）自带的，位于 /usr/local
中，要设置</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="err">LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/mysql/lib</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>否则找不到 libmysqlclient.so.20 动态链接文件。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->

<p>部署方式是 gunicorn+nginx，gunicorn 的 supervisord 配置如下：</p>
<!-- /wp:paragraph -->

<!-- wp:code -->

<div class="highlight"><pre><span></span><code><span class="k">[program:app]</span>
<span class="na">user</span><span class="o">=</span><span class="s">someuser</span>
<span class="na">environment</span><span class="o">=</span><span class="s">HOME=&quot;/home/someuser&quot;,USER=&quot;someuser&quot;,LD_LIBRARY_PATH=&quot;/usr/local/mysql/lib&quot;</span>
<span class="na">directory</span><span class="o">=</span><span class="s">/home/someuser/app</span>
<span class="na">command</span><span class="o">=</span><span class="s">/home/someuser/.virtualenvs/app/bin/gunicorn hseback.wsgi</span>

<span class="na">stdout_logfile</span><span class="o">=</span><span class="s">/var/log/supervisor/app.out</span>
<span class="na">stdout_logfile_maxbytes</span><span class="o">=</span><span class="s">10MB</span>
<span class="na">stdout_logfile_backups</span><span class="o">=</span><span class="s">10</span>

<span class="na">stderr_logfile</span><span class="o">=</span><span class="s">/var/log/supervisor/app.err</span>
<span class="na">stderr_logfile_maxbytes</span><span class="o">=</span><span class="s">10MB</span>
<span class="na">stderr_logfile_backups</span><span class="o">=</span><span class="s">10</span>
</code></pre></div>


<!-- /wp:code -->

<!-- wp:paragraph -->

<p>如果部署为 HTTP 访问，应该关闭 Django 的 SECURE_SSL_REDIRECT
选项，否则只会得到服务器返回的 301 代码。</p>
<!-- /wp:paragraph -->
    </div>
<nav class="nav sidebar">
    <div class="siteinfo">
        <div class="sitename"><a href="..">coinfaces</a></div>
        <div class="tagline">Another Side of Life</div>
    </div>

        <a href="../categories/">categories</a>
        <a href="..">&larr; Home</a>
</nav><footer class="footer">
    <span>
        &copy; jpli &middot;
        under <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
    </span>
    |
    <span>
        Powered by:
        <a href="http://blog.getpelican.com/">Pelican</a>
    </span>
</footer></body>
</html>